import protobuf from 'protobufjs'
import Long from 'long'
import rpc_msg from './rpc_msg.json'
import internet_radio from './internet_radio.json'
import internet_radio_db_list from './internet_radio_db.list.json'
import {state} from "../store/state";
import bfConsole from "../../utils/bf-console";
import bfEvent from "../../utils/bf-event";
import bfstore from "../../js/bf-store";

let Root = protobuf.Root;
protobuf.util.Long = Long;
protobuf.configure();

let proto = Root.fromJSON(rpc_msg);
proto = Root.fromJSON(internet_radio, proto);
proto = Root.fromJSON(internet_radio_db_list, proto);

// 私有属性，rpc-cmd命令所需
let seqNo: number = 0;
let rpcId: number = 0;
let rpcCmdName: string = "rpc_cmd";
let protoRootName: string = "bfproto.";

// webSocket 连接相关配置
let socket: null | WebSocket = null;
// 超时机制回调事件对象集
let msgObserver: object = {};
// 自动重连的总次数
let connectCount: number = 0;
// ping服务器的定时器id
let pingInterval: number = 0;
// ping成功的次数
let heartBeatCount: number = 0;
// 在ping命令同步服务器时间的毫秒数
let syncSeverTime: number = 0;

class BfProto implements bfProto {

  getSeqNo(): number {
    if (seqNo > 200000000) {
      seqNo = 0;
    }
    return ++seqNo;
  }

  getRpcId(): number {
    if (rpcId > 200000000) {
      rpcId = 0;
    }
    return ++rpcId;
  }

  // 使用proto消息名称获取对应的消息类型
  getType(typeName: string): any {
    let msg_name = protoRootName + typeName;
    let Tmsg = proto.lookup(msg_name);

    return Tmsg;
  }

  // 格式化rpc-cmd通用属性
  newRpcCmd(rpcType?: any): any {
    let rpc_cmd_t: any = rpcType || this.getType(rpcCmdName);
    let rpc_cmd_obj: any = rpc_cmd_t.create();
    rpc_cmd_obj.rpcId = this.getRpcId();
    rpc_cmd_obj.seqNo = this.getSeqNo();
    rpc_cmd_obj.sysId = state.sysInfo.sysId;
    rpc_cmd_obj.sid = state.userInfo.ssid;

    return rpc_cmd_obj;
  }

  // 序列化rpc-cmd消息为unit8Array类型
  encodeMessage(command: number, message: object, typeName: string): any {
    // 获取rpc_cmd和要发送的数据proto类型
    let rpc_cmd_t: any = this.getType(rpcCmdName);
    let rpc_cmd_obj: any = this.newRpcCmd(rpc_cmd_t);
    let db_controller_t: any = this.getType(typeName);

    // 使用verify验证消息字段类型
    let errMsg = db_controller_t.verify(message);
    if (errMsg) {
      bfConsole.error("encodeRpcMessage error:", errMsg);
      return Promise.reject(errMsg);
    }
    // 造成unit8Array数据
    let db_controller_obj = db_controller_t.create(message);
    rpc_cmd_obj.command = command;
    rpc_cmd_obj.body = db_controller_t.encode(db_controller_obj).finish();
    let rpc_cmd_bytes = rpc_cmd_t.encode(rpc_cmd_obj).finish();

    // 使用Promise类返回unit8Array
    return Promise.resolve({
      buffer: rpc_cmd_bytes,
      replyTo: `${rpc_cmd_obj.command}@${rpc_cmd_obj.rpcId}`
    });
  }

  decode(msgBuf: Uint8Array, typeName: string): any {
    let msg_obj_t: any = this.getType(typeName);

    return msg_obj_t.decode(msgBuf);
  }

  // 反序列化rpc-cmd消息
  decodeMessage(msgBuf: Uint8Array, typeName: string): any {
    // 反序列化rpc-cmd消息
    let rpc_cmd_obj: any = this.decode(msgBuf, rpcCmdName);

    // 反序列化body消息
    rpc_cmd_obj.body = this.decode(rpc_cmd_obj.body, typeName);

    return Promise.resolve(rpc_cmd_obj);
  }

  // 解析webSocket消息
  onmessage(buffer: Uint8Array): void {
    //websocket直接传输二进制，不需要进行二进制字符串转换
    let rpc_cmd_obj = this.decode(buffer, rpcCmdName);
    let replyTo = `${rpc_cmd_obj.command}@${rpc_cmd_obj.rpcId}`;

    // 清除超时回调
    clearTimeout(msgObserver[replyTo]);
    delete msgObserver[replyTo];

    //todo 有些命令可能只有rpc_cmd的消息类型，需要特殊处理，如错误消息
    switch (rpc_cmd_obj.command) {
      case 0:
        break;
    }

    // 发布一个 replayTo 消息事件，各组件订阅该命令事件及响应
    bfEvent.emit(replyTo, rpc_cmd_obj);
  }

  // 使用webSocket连接服务器，可自动重连20次
  connect(): void {
    // 设置websocket地址
    let location = window.location;
    let hostname = location.hostname;
    let protocol = location.protocol == "https" ? "wss" : "ws";
    let port = location.port;
    if (!port) {
      port = protocol == 'https' ? "443" : "80";
    }
    let addr = `${protocol}://${hostname}:${port}/socket`;
    addr = "ws://t2.bfdx.net:4230";
    addr = "ws://localhost:8200/socket";

    // 开始连接服务器
    socket = new WebSocket(addr);
    socket.binaryType = 'arraybuffer';
    socket.onopen = () => {
      // 标记开始连接服务器
      bfEvent.emit("socketConnected");
      connectCount = 0;
    };
    socket.onmessage = (evt) => {
      if (evt.data == null) {
        return
      }

      let data: Uint8Array = new Uint8Array(evt.data);
      this.onmessage(data);
    };
    socket.onclose = () => {
      bfEvent.emit("socketDiconnect");
      // 默认每3秒重连一次服务器，上限20次
      if (connectCount < 20) {
        setTimeout(() => {
          connectCount++;
          bfConsole.warn("socket connect count:", connectCount);
          this.connect();
        }, 3000)
      }
    };
  }

  // 使用webSocket将数据发送到服务器前，建立超时及监听服务器响应机制逻辑
  send(buffer: Uint8Array, option: socketSendOption): any {
    // 如果socket没有正确连接，则返回发送错误
    if (!socket || socket.readyState !== 1) {
      return Promise.reject("webSocket is not open!");
    }

    // 解构对象参数
    let {
      replyTo, timeout = 60,
      subcontract = false, subject,
      decodeMsgType
    } = option;

    // 监听服务器响应成功后，返回数据并解绑监听此命令事件
    let resFunc = (_rpc_cmd_obj) => {
      _rpc_cmd_obj.body = decodeMsgType ? this.decode(_rpc_cmd_obj.body, decodeMsgType) : _rpc_cmd_obj.body;
      if (subcontract && subject) {
        // 服务器分包返回数据，使用emit触发消息事件
        bfEvent.emit(subject, _rpc_cmd_obj);
        //todo 响应服务器，客户端已收到消息事件
      } else {
        bfEvent.off(replyTo, resFunc);
        return Promise.resolve(_rpc_cmd_obj);
      }
    };
    bfEvent.on(replyTo, resFunc);

    // 超时机制
    msgObserver[replyTo] = setTimeout(() => {
      if (!msgObserver[replyTo]) {
        return
      }
      delete msgObserver[replyTo];

      // 超时后解绑rpcId的自定义事件，避免出现rpcId循环重复的耦合bug
      bfEvent.off(replyTo, resFunc);
      return Promise.reject("server response timeout!");
    }, timeout * 1000);

    // 发送数据到服务器
    socket.send(buffer);

    // ping服务器，保持与服务器连接
    this.keepWebSocketConnect();
  }

  // 使用proto协议序列化数据后使用webSocket发送到服务器
  sendMessage(command: number, message: object, typeName: string, opts?: socketSendOption): any {
    // 将需要发送的数据序列化
    return this.encodeMessage(command, message, typeName).then((res: any) => {
      // 合并发送数据时所需的配置属性
      let option: socketSendOption = Object.assign({
        msgType: typeName,
        replyTo: res.replyTo
      }, opts || {});

      // 将序列化后的Uint8Array数据以webSocket发送到服务器
      return this.send(res.buffer, option);
    });
  }

  // 保持webSocket与服务器的心跳连接
  keepWebSocketConnect(): void {
    // ping前先清除上次的定时器
    clearTimeout(pingInterval);

    // 配置ping参数
    let message = {
      pingCode: heartBeatCount % 10 ? 1 : 2,
      pong: 1,
    };
    let opts: socketSendOption = {
      decodeMsgType: "ping"
    };

    // 定时器handler方法
    let fn = () => {
      this.sendMessage(20, message, "ping", opts).then(res => {
        bfConsole.error("keepWebSocketConnect succ:", res);
        heartBeatCount++;
        syncSeverTime = Date.now();
      }).catch(err => {
        bfConsole.error("keepWebSocketConnect fail:", err);
      });
    };

    // 如果上次同步服务器时间与现在相隔45分钟，则必发一次命令同步器时间
    if (Date.now() - syncSeverTime > 2700000) {
      heartBeatCount = 0;
      message.pingCode = 2;
      fn();
    }

    // 定时心跳连接,4'30''一次
    pingInterval = window.setInterval(fn, 270000);
  }

}

export default new BfProto();

